#pragma once

#ifndef __CORE_SmartPtr_H__
#define __CORE_SmartPtr_H__

#include "FLOW_core/static_conversion_check.h"

namespace CORE
{
	/*!
	 * \brief SmartPtr that frees the pointer held when nobody else references it
	 *
	 * SmartPtr that frees the pointer hold when nobody else references it
	 */
	template< typename HeldType >
	class SmartPtr
	{
	public:
		//========================= METHODS ===================================
		//! Default contructor
		SmartPtr( ) : m_p_pointee( NULL ) {	}

		//! Destructor
		~SmartPtr( )
		{
			DecRef( );
		}

		//! Constructor from raw pointer
		SmartPtr( HeldType* ap_pointee ) : m_p_pointee( ap_pointee )
		{
			AddRef( );
		}

		//! Constructor for convertible raw pointer
		template< typename ConvertibleHeldType >
		SmartPtr( ConvertibleHeldType* ap_pointee ) : m_p_pointee( ap_pointee )
		{
			STATIC_ASSERT( CONVERTIBLE_POINTER_TYPES( HeldType, ConvertibleHeldType ), the_type_is_not_convertible );
			AddRef( );
		}

		//! Copy constructor of the same type
		SmartPtr( const SmartPtr< HeldType >& ar_smart_ptr )
		{
			m_p_pointee = ar_smart_ptr.m_p_pointee;
			AddRef( );
		}

		//! Copy constructor for derived or casteable types
		template< typename ConvertibleHeldType >
		SmartPtr( const SmartPtr< ConvertibleHeldType >& ar_smart_ptr )
		{
			STATIC_ASSERT( CONVERTIBLE_POINTER_TYPES( HeldType, ConvertibleHeldType ), the_type_is_not_convertible );
			m_p_pointee = const_cast< ConvertibleHeldType* >( ar_smart_ptr.GetPtr( ) );
			AddRef( );
		}

		//! Assignment operator for a raw pointer
		SmartPtr& operator = ( HeldType* ap_pointee )
		{
			DecRef( );
			m_p_pointee = ap_pointee;
			AddRef( );
			return *this;
		}

		//! Assignment operator for a raw pointer of a convertible pointer type
		template< typename ConvertibleHeldType >
		SmartPtr& operator = ( ConvertibleHeldType* ap_pointee )
		{
			STATIC_ASSERT( CONVERTIBLE_POINTER_TYPES( HeldType, ConvertibleHeldType ), the_type_is_not_convertible );
			DecRef( );
			m_p_pointee = ap_pointee;
			AddRef( );
			return *this;
		}

		//! Assignment operator for a smart pointer of the same type
		SmartPtr& operator = ( const SmartPtr< HeldType >& ar_smart_ptr )
		{
			DecRef( );
			m_p_pointee = const_cast< HeldType* >( ar_smart_ptr.GetPtr( ) );
			AddRef( );
			return *this;
		}

		//! Assignment operator for a smart pointer of a convertible type
		template< typename ConvertibleHeldType > 
		SmartPtr& operator = ( const SmartPtr< ConvertibleHeldType >& ar_smart_ptr )
		{
			STATIC_ASSERT( CONVERTIBLE_POINTER_TYPES( HeldType, ConvertibleHeldType ), the_type_is_not_convertible );
			DecRef( );
			m_p_pointee = ar_smart_ptr.GetPtr( );
			AddRef( );
			return *this;
		}

		//! Dereference operator
		HeldType* operator -> ( void ) const
		{
			return m_p_pointee;
		}

		//! Validity check method
		bool_t IsValid( void ) const
		{
			return m_p_pointee != NULL;
		}

		//! Accesor to the raw pointer
		const HeldType* GetPtr( void ) const
		{
			return m_p_pointee;
		}

		HeldType* GetPtr( void )
		{
			return m_p_pointee;
		}

		//! Equality operator for a raw pointer
		bool_t operator == ( const HeldType* ap_raw_pointer ) const
		{
			return m_p_pointee == ap_raw_pointer;
		}

		//! Equality operator for a raw pointer of a convertible type
		template< typename ConvertibleHeldType >
		bool_t operator == ( const ConvertibleHeldType* ap_raw_pointer ) const
		{
			STATIC_ASSERT( CONVERTIBLE_POINTER_TYPES( HeldType, ConvertibleHeldType ), the_type_is_not_convertible );
			return m_p_pointee == ap_raw_pointer;
		}

		//! Equality operator for a smart pointer of the same type
		bool_t operator == ( const SmartPtr< HeldType >& ar_smart_ptr ) const
		{
			return m_p_pointee == ar_smart_ptr.m_p_pointee;
		}

		//! Equality operator for a smart pointer of a convertible type
		template< typename ConvertibleHeldType >
		bool_t operator == ( const SmartPtr< ConvertibleHeldType >& ar_smart_ptr ) const
		{
			STATIC_ASSERT( CONVERTIBLE_POINTER_TYPES( HeldType, ConvertibleHeldType ), the_type_is_not_convertible );
			return m_p_pointee == ar_smart_ptr.GetPtr( );
		}

		//! Distinct operator for any type
		template< typename AnyType >
		bool_t operator != ( const AnyType* ar_any_type ) const
		{
			return !operator == ( ar_any_type );
		}

		//! Distinct operator for a smart pointer of any type
		template< typename AnyType >
		bool_t operator != ( const SmartPtr< AnyType >& ar_smart_ptr_any_type ) const
		{
			return ! operator == ( ar_smart_ptr_any_type );
		}
		
	private:
		//========================== METHODS ==================================
		//! Adds a reference to the held pointer if it's valid
		void AddRef( void )
		{
			if( m_p_pointee )
				m_p_pointee->AddRef( );
		}

		//! Decrement a reference from the hold pointer if it's valid, if this is the last reference
		//! deletes the pointer
		void DecRef( void )
		{
			if( m_p_pointee )
			{
				m_p_pointee->DecRef( );
				if( m_p_pointee->GetRefCount( ) == 0 )
					SAFE_DELETE( m_p_pointee );
			}
		}
		//========================= DATA MEMBERS ==============================
		//! Held pointer
		HeldType* m_p_pointee;
	};

	template< typename HeldType, typename AnyType >
	bool_t operator == ( const AnyType* ap_raw_ptr, const SmartPtr< HeldType >& ar_smart_ptr )
	{
		return ar_smart_ptr == ap_raw_ptr;
	}

	template< typename HeldType, typename AnyType >
	bool_t operator != ( const AnyType* ar_any_type, const SmartPtr< HeldType >& ar_smart_ptr )
	{
		return ar_smart_ptr != ar_any_type;
	}

} //namespace CORE

#endif
